package omg.loot;

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Random;

import javax.imageio.ImageIO;

public class Map {
   public static int WIDTH; //Map width in tiles
   public static int HEIGHT; //Map height in tiles
   Random rami = new Random();

   protected Tile tileMap[][];
   protected HashSet<Tile> open = new HashSet<Tile>();
   protected HashSet<Tile> closed = new HashSet<Tile>();
   protected LinkedList<Tile> q = new LinkedList<Tile>();
   
   protected Tile tile;
   protected boolean right = true;
   protected Tile small;
   protected BufferedImage maps[];
   protected String filename = "/omg/loot/gfx/map0.bmp";

   public Map(){
      tileMap = newMap(readMap());
   }

   public Tile aCross(Tile start, Tile end){
      closed = new HashSet<Tile>();
      
      Tile current = tileMap[end.x][end.y];
      closed.add(current);
      current.distance = 0;
      
      q.offer(current);
      while(q.peek() != null){
         checkParents(current);
         current = q.poll();
         addToQueue(current);
      }
      return (tileMap[start.x][start.y]);
   }
   
   public void addToQueue(Tile t){
      if(t.y > 0 && (t.distance+1) < tileMap[t.x][t.y-1].distance && tileMap[t.x][t.y-1].type == Tile.LAND){
         if(closed.add(tileMap[t.x][t.y-1]))
            updateTile(tileMap[t.x][t.y-1],t);
      }
      if(t.x < (WIDTH-1) && (t.distance+1) < tileMap[t.x+1][t.y].distance && tileMap[t.x+1][t.y].type == Tile.LAND){
         if(closed.add(tileMap[t.x+1][t.y]))
            updateTile(tileMap[t.x+1][t.y],t);
      }
      if(t.y < (HEIGHT-1) && (t.distance+1) < tileMap[t.x][t.y+1].distance && tileMap[t.x][t.y+1].type == Tile.LAND){
         if(closed.add(tileMap[t.x][t.y+1]))
            updateTile(tileMap[t.x][t.y+1],t);
      }
      if(t.x > 0 && (t.distance+1) < tileMap[t.x-1][t.y].distance && tileMap[t.x-1][t.y].type == Tile.LAND){
         if(closed.add(tileMap[t.x-1][t.y]))
            updateTile(tileMap[t.x-1][t.y],t);
      }
   }
   
   public void updateTile(Tile current, Tile parent){
      current.parent[current.parentIndex] = parent;
      current.parentIndex++;
      current.distance = parent.distance + current.stepCost;
      q.offer(current);
   }
   
   public Tile[] returnPath(Tile t){
      ArrayList<Tile> path = new ArrayList<Tile>();
      while(t.parent[0] != null){
         path.add(t);
         t = t.parent[0];
      }
      path.add(t);
      return path.toArray(new Tile[path.size()]);
   }
   
   public void checkParents(Tile t){
      try{
      if(t.y > 0 && (tileMap[t.x][t.y-1].distance <= t.parent[0].distance)){
         t.parent[t.parentIndex] = tileMap[t.x][t.y-1];
         t.parentIndex++;
      }
      }catch(Exception e){}
      try{
      if(t.x < (WIDTH-1) && (tileMap[t.x+1][t.y].distance <= t.parent[0].distance)){
         t.parent[t.parentIndex] = tileMap[t.x+1][t.y];
         t.parentIndex++;
      }
      }catch(Exception e){}
      try{
      if(t.y < (HEIGHT-1) && (tileMap[t.x][t.y+1].distance <= t.parent[0].distance)){
         t.parent[t.parentIndex] = tileMap[t.x][t.y+1];
         t.parentIndex++;
      }
      }catch(Exception e){}
      try{
      if(t.x > 0 && (tileMap[t.x-1][t.y].distance <= t.parent[0].distance)){
         t.parent[t.parentIndex] = tileMap[t.x-1][t.y];
         t.parentIndex++;
      }
      }catch(Exception e){}
   }
   
   /**
    * Basic implementation of A* pathfinding algorithm, for more info
    * check the links below:
    * http://www.policyalmanac.org/games/aStarTutorial.htm
    * http://theory.stanford.edu/~amitp/GameProgramming/
    * 
    * @param start Tile where the path starts
    * @param end Tile where the path ends
    */
   public Tile[] aStar(final Tile start, final Tile end){
      open = new HashSet<Tile>();
      closed = new HashSet<Tile>();
      small = new Tile();
      small.totalCost=Integer.MAX_VALUE;

      Tile current = tileMap[start.x][start.y];
      current.distance = manhattanDistance(start,end);
      current.totalCost = current.stepCost + current.distance;

      final Tile smallest = new Tile();
      smallest.distance = Integer.MAX_VALUE;

      open.add(current);
      while(!open.isEmpty()){
         if(current.x == end.x && current.y == end.y){
            final ArrayList<Tile> path = path(current);
            path.add(0,start);
            return path.toArray(new Tile[path.size()]);
         }

         open.remove(current);
         closed.add(current);
         addToOpenList(current, start, end, smallest);
         current = getNextStep();

      }
      return new Tile[0];
   }

   /**
    * Wrapper to addToOpen
    * @see addToOpen
    */
   public void addToOpenList(final Tile current, final Tile start, final Tile end, final Tile smallest){
      if(current.y > 0)
         addToOpen(current.x, current.y-1, current, start, end, smallest);
      if(current.x < 99)
         addToOpen(current.x+1, current.y, current, start, end, smallest);
      if(current.y < 99)
         addToOpen(current.x, current.y+1, current, start, end, smallest);
      if(current.x > 0)
         addToOpen(current.x-1, current.y, current, start, end, smallest);
   }//addToOpenList

   /**
    * If node has not already been processed:
    *    if in open list
    *       check if new route is faster, update accordingly
    *    else update route and add to list of open nodes
    * 
    * @param x x coordinate of current node
    * @param y y coordinate of current node
    * @param parent Parent of current node
    * @param start Begining of path
    * @param end End of path
    * @param smallest The most promising tile for next step
    */
   public void addToOpen(final int x, final int y, final Tile parent, final Tile start, final Tile end, final Tile smallest){
      final Tile current = tileMap[x][y];

      if(!closed.contains(current)){
         if(open.contains(current) ){
            final int tmpG = parent.stepCost + 10;
            if(tmpG < current.stepCost){
               current.stepCost = tmpG;
               current.totalCost = current.stepCost + current.distance;
            }
         }else{
            if(current.type == Tile.LAND){
               current.distance = manhattanDistance(current,end);
               current.stepCost += parent.stepCost;
               current.totalCost = current.stepCost + current.distance;
               current.parent[current.parentIndex] = parent;
               current.parentIndex++;
               open.add(current);
            }
         }
      }
   }

   /**
    * Calculates manhattan distance between two tiles
    * @param start Starting tile 
    * @param end  End tile
    * @return manhattan distance between start and end
    */
   public int manhattanDistance(final Tile start, final Tile end){
      return Math.abs(end.x - start.x)+Math.abs(end.y-start.y);
   }

   /**
    * Backtracks the path from end point to starting point
    * 
    * @param current Ending point of aStar tile of the path
    * @return Sortest path to node start
    */
   public ArrayList<Tile> path(Tile current){
      final ArrayList<Tile> path = new ArrayList<Tile>();
      while(current.parent[0] != null){
         path.add(0,new Tile(current));
         current = current.parent[current.parentIndex];
      }
      return path;
   }

   public Tile getNextStep(){
      Tile smallest = new Tile();
      smallest.totalCost = Integer.MAX_VALUE;
      for(final Tile t : open){
         if(t.totalCost < smallest.totalCost)
            smallest = t;
      }
      return smallest;
   }

   public BufferedImage readMap(){
      BufferedImage img = null;
      try {
         img = ImageIO.read(new File(this.getClass().getResource(filename).toURI()));
      } catch (final Exception e) {
         System.out.println(e);
      }
      return img;
   }

   public Tile[][] newMap(final BufferedImage img){
      WIDTH = img.getWidth();
      HEIGHT = img.getHeight();
      Tile[][] mapTemp = new Tile[WIDTH][HEIGHT];
      for(int x = 0; x < WIDTH; x++){
         for(int y = 0; y < HEIGHT; y++){
            mapTemp[x][y] = new Tile(x,y);            
            switch(img.getRGB(x, y)){
               case Tile.SWAMP:{
                  mapTemp[x][y].type = Tile.SWAMP;
                  break;
               }
               case Tile.WATER:{
                  mapTemp[x][y].type = Tile.WATER;
                  break;
               }
               default:
                  mapTemp[x][y].type = Tile.LAND;
                  break;
            }
         }
      }//outer for
      return mapTemp;
   }
}